//-------------------------------------------------------------------
//	Copyright (c) 2012-2014 Zhirnov Andrey
//	This file is part of the "UXGEN-ES" project.
//	See copyright notice in "Engine.h".
//-------------------------------------------------------------------

#include "Platforms/Windows/WPlatform.h"

#if defined( PLATFORM_WINDOWS )

#pragma comment (lib, "opengl32.lib")

namespace Engine
{
namespace Platforms
{

	WindowsPlatform *	WindowsPlatform::_instance = null;
	
/*
=================================================
	constructor
=================================================
*/
	WindowsPlatform::WindowsPlatform () :
		_pause(NO_FOCUS), _wnd(null), _dc(null), _rc(null), _isLooping(false), _requiredDeviceType( EDevice::AUTO ),
		_backgroundMode(false), _terminated(false)
	{
		_timer.Start();

		_directories[ EDirectory::INTERNAL ]			= "internal";
		_directories[ EDirectory::INTERNAL_CACHE ]		= "internal_cache";
		_directories[ EDirectory::EXTERNAL ]			= "external";
		_directories[ EDirectory::EXTERNAL_CACHE ]		= "external_cache";
		_directories[ EDirectory::EXTERNAL_STORAGE ]	= "sdcard";
	}

/*
=================================================
	destructor
=================================================
*/
	WindowsPlatform::~WindowsPlatform ()
	{
	}
	
/*
=================================================
	SendEvent
=================================================
*/
	void WindowsPlatform::SendEvent (const SysEvent &ev)
	{
		_application->GetSubSystems()->GetEventSystem()->Send( ev );
	}
	
/*
=================================================
	SendEventDelayed
=================================================
*/
	void WindowsPlatform::SendEventDelayed (const SysEvent &ev)
	{
		ParallelOp	op;
		FunctionBuilder::Create( op.func, _application->GetSubSystems()->GetEventSystem(), &EventSystem::Send, ev );

		_application->Push( op );
	}

/*
=================================================
	Loop
=================================================
*/
	int WindowsPlatform::Loop ()
	{
		CHECK_ERR( _Init(),		  -1 );
		
		_EnterForeground( false );
		CHECK_ERR( _RegisterClass(), -2 );
		CHECK_ERR( _CreateWindow(),  -3 );
		CHECK_ERR( _InitRawInput(),  -4 );
		_EnterForeground( true );

		CHECK_ERR( _EventLoop(),     -5 );

		_EnterBackground( false );

		// On Destroy Surface
		if ( _dc != null and _rc != null )
		{
			TSysEvent< ESystemEvent::APPLICATION >	ev;

			ev.app.type	= SysEvent::Application::SURFACE_DESTROYED;
			SendEvent( ev );
	
			gles::GLES_Delete();
		}

		_Destroy();
		_EnterBackground( true );
		EnableAudio( false );

		// TODO: work on background

		if ( _backgroundMode )
		{
			_BackgroundLoop();
		}
		
		if ( _application.IsNotNull() )
		{
			TSysEvent< ESystemEvent::APPLICATION >	ev;

			ev.app.type = SysEvent::Application::DESTROY;
			SendEvent( ev );

			_application->Destroy();
			_application = null;
		}

		return 0;
	}
	
/*
=================================================
	InitRender
=================================================
*/
	bool WindowsPlatform::InitRender (INOUT VideoSettings &vs)
	{
		CHECK_ERR( _InitOpenGL( vs ) );
		
		// Initialize OpenGL //
		CHECK_ERR( wglMakeCurrent( _dc, _rc ) == winapi::TRUE );
		
		if ( _ext.swapControlSupported )
		{
			_ext.wglSwapInterval( 1 );
		}
		
		{
			using namespace gles;

			bool gles_initialized = GLES_Init();

			if ( gles_initialized )
			{
				string	gpu;

				const char	*	vendor		= null,
							*	renderer	= null,
							*	version		= null,
							*	sl_version	= null;

				GL_CALL( vendor		= (const char *) glGetString( GL_VENDOR ) );
				GL_CALL( renderer	= (const char *) glGetString( GL_RENDERER ) );
				GL_CALL( version	= (const char *) glGetString( GL_VERSION ) );
				GL_CALL( sl_version	= (const char *) glGetString( GL_SHADING_LANGUAGE_VERSION ) );
				
				gpu << vendor << ' ' << renderer << ", ver: " << version << ", SL ver: " << sl_version;
				_device.SetGPUName( gpu );
				_device.WriteToLog();
			}
		}

		TSysEvent< ESystemEvent::APPLICATION >	ev;

		ev.app.type	= SysEvent::Application::SURFACE_CREATED;

		SendEventDelayed( ev );

		return true;
	}
	
/*
=================================================
	SwapBuffers
=================================================
*/
	void WindowsPlatform::SwapBuffers ()
	{
		const bool	has_rc = ( _rc != null and _dc != null );

		if ( has_rc )
		{
			winapi::SwapBuffers( _dc );
		}
	}
	
/*
=================================================
	IsRenderingAvailable
=================================================
*/
	bool WindowsPlatform::IsRenderingAvailable ()
	{
		return	_dc != null and
				_rc != null and
				_rc == winapi::wglGetCurrentContext();
	}
	
/*
=================================================
	EnableAudio
=================================================
*/
	void WindowsPlatform::EnableAudio (bool enabled)
	{
		TSysEvent< ESystemEvent::AUDIO >	se;

		se.audio.type	= enabled ? SysEvent::Audio::SET_FOCUS : SysEvent::Audio::KILL_FOCUS;

		SendEvent( se );
	}
	
/*
=================================================
	CheckLicense
=================================================
*/
	void WindowsPlatform::CheckLicense (const OnLicenseChecked_t &callback, uni_c_string publicKey)
	{
		TODO( "CheckLicense" );

		callback.Call( ELicenseCheckResponse::LICENSED, "" );
	}

/*
=================================================
	Terminate
=================================================
*/
	void WindowsPlatform::Terminate ()
	{
		ASSERT( not _terminated );

		_terminated = true;

		if ( _isLooping )
		{
			_pause |= ON_DESTROY;
			PostMessageA( _wnd, WM_CLOSE, 0, 0 );
		}

		_backgroundMode = false;
	}

/*
=================================================
	InitDevice
=================================================
*/
	void WindowsPlatform::InitDevice (EDevice::type deviceType)
	{
		if ( deviceType != EDevice::AUTO )
		{
			DeviceEmulator::Create( _device, deviceType );

			// if window created //
			if ( _wnd != null )
			{
				_windowSize = _device.GetDisplay().Resolution();
				
				DWORD dwStyle	= (DWORD) GetWindowLongA( _wnd, GWL_STYLE );
				DWORD dwExStyle	= (DWORD) GetWindowLongA( _wnd, GWL_EXSTYLE );

				RECT	winRect = { 0, 0, _windowSize.x, _windowSize.y };
				AdjustWindowRectEx( &winRect, dwStyle, FALSE, dwExStyle );

				uvec2 const	res = _GetScreenResolution();
				ivec2		pos;
				
				pos.x = ( res.x - (winRect.right  - winRect.left) ) / 2;
				pos.y = ( res.y - (winRect.bottom - winRect.top) ) / 2;

				/*SetWindowPos( _wnd, HWND_TOP, pos.x, pos.y, winRect.right - winRect.left,
							  winRect.bottom - winRect.top, SWP_FRAMECHANGED );

				_OnResized( _windowSize.x, _windowSize.y );*/
			}
			else
			if ( IsNotZero( _windowSize ) )
			{
				_device.GetDisplayRef().SetResolution( _windowSize );
			}
			else
			{
				_windowSize = _device.GetDisplay().Resolution();
			}
		}
		
		_requiredDeviceType = deviceType;
		_device.WriteToLog();
	}
	
/*
=================================================
	SetDisplayOrientation
=================================================
*/
	void WindowsPlatform::SetDisplayOrientation (EDisplayOrientation::type orientation)
	{
		TSysEvent< ESystemEvent::WINDOW >	se;

		se.window.type = SysEvent::Window::ORIENTATION_CHANGED;

		switch ( orientation )
		{
			case EDisplayOrientation::DEFAULT :
			case EDisplayOrientation::SENSOR :
			case EDisplayOrientation::PORTRAIT :
				se.window.orientation = Display::ORIENT_0;
				break;

			case EDisplayOrientation::PORTRAIT_REVERSE :
				se.window.orientation = Display::ORIENT_180;
				break;

			case EDisplayOrientation::LANDSCAPE :
				se.window.orientation = Display::ORIENT_90;
				break;

			case EDisplayOrientation::LANDSCAPE_REVERSE :
				se.window.orientation = Display::ORIENT_270;
				break;
		}
		
		_device.GetDisplayRef().SetOrientation( se.window.orientation );

		SendEvent( se );

		_Resize( _device.GetDisplay().Resolution().yx() );
	}

/*
=================================================
	BackgroundMode
=================================================
*
	void WindowsPlatform::BackgroundMode (bool enabled)
	{
		_backgroundMode = enabled;
	}

/*
=================================================
	SendEmail
=================================================
*/
	void WindowsPlatform::SendEmail (uni_c_string address, uni_c_string subject,
									 uni_c_string text, uni_c_string attachment)
	{
		TODO( "SendEmail" );
	}
	
/*
=================================================
	OpenURL
=================================================
*/
	void WindowsPlatform::OpenURL (uni_c_string url)
	{
		PlatformUtils::OpenURL( url );
	}

/*
=================================================
	ShowNotification
=================================================
*/
	void WindowsPlatform::ShowNotification (uni_c_string text, bool longTime)
	{
		TODO( "ShowNotification" );
	}
		
/*
=================================================
	ShowMessage
=================================================
*/
	void WindowsPlatform::ShowMessage (const OnMessageResult_t &callback,
										uni_c_string caption, uni_c_string text,
										uni_c_string positive, uni_c_string negative)
	{
		TODO( "ShowMessage" );
	}
	
/*
=================================================
	GetEventSystem
=================================================
*/
	EventSystemPtr WindowsPlatform::GetEventSystem ()
	{
		if ( _application.IsNull() )
			return null;

		return _application->GetSubSystems()->GetEventSystem().ptr();
	}
	
/*
=================================================
	GetThread
=================================================
*/
	IParallelThreadPtr WindowsPlatform::GetThread ()
	{
		if ( _application.IsNull() )
			return null;

		return _application->GetSubSystems()->GetCurrentThread().ptr();
	}

/*
=================================================
	CreateInstance
=================================================
*/
	void WindowsPlatform::CreateInstance ()
	{
		if ( _instance == null )
		{
			_instance = new WindowsPlatform();
		}
	}
	
/*
=================================================
	DestroyInstance
=================================================
*/
	void WindowsPlatform::DestroyInstance ()
	{
		if ( _instance != null )
		{
			delete _instance;
			_instance = null;
		}
	}
	
/*
=================================================
	_RegisterClass
=================================================
*/
	bool WindowsPlatform::_RegisterClass ()
	{
		HMODULE			instance	= _GetInstance();
		const char *	class_name	= _GetClassName();

		WNDCLASSA	tmp = {0};
		bool		ret = GetClassInfo( instance, class_name, &tmp ) == TRUE;
		
		if ( ret or ( class_name == tmp.lpszClassName && instance == tmp.hInstance ) )
			return true;

		WNDCLASSEXA		windowClass = {0};
		windowClass.cbSize			= sizeof(windowClass);
		windowClass.style			= CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
		windowClass.lpfnWndProc		= &DefWindowProcA;
		windowClass.cbClsExtra		= 0;
		windowClass.cbWndExtra		= 0;
		windowClass.hInstance		= instance;
		windowClass.hbrBackground	= (HBRUSH) COLOR_WINDOWFRAME;
		windowClass.hIcon			= (HICON) null;
		windowClass.hCursor			= LoadCursor( null, IDC_ARROW );
		windowClass.lpszMenuName	= null;
		windowClass.lpszClassName	= class_name;
		windowClass.hIconSm			= (HICON) null;

		CHECK_ERR( RegisterClassEx( &windowClass ) != 0 );

		return true;
	}
	
/*
=================================================
	_GetScreenResolution
=================================================
*/
	uvec2 WindowsPlatform::_GetScreenResolution ()
	{
		enum {
			SM_CXSCREEN	= 0,
			SM_CYSCREEN	= 1,
		};
		return uvec2( GetSystemMetrics( SM_CXSCREEN ),
					  GetSystemMetrics( SM_CYSCREEN ) );
	}

/*
=================================================
	_CreateWindow
=================================================
*/
	bool WindowsPlatform::_CreateWindow ()
	{
		Display const &	disp	= _device.GetDisplay();
		const ivec2		scr_res	= _GetScreenResolution().To<ivec2>();

		ivec2	v_wnd_pos;
		ivec2	v_wnd_size	= Clamp( ivec2(12), disp.Resolution(), scr_res );

		DWORD	wndStyle	= WS_OVERLAPPEDWINDOW;// | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX;
		DWORD	wndExtStyle	= WS_EX_APPWINDOW;

		CHECK_ERR( _RegisterClass() );
		
		RECT winRect = { 0, 0, v_wnd_size.x, v_wnd_size.y };
		AdjustWindowRectEx( &winRect, wndStyle, 0, wndExtStyle );

		v_wnd_pos.x = Max( 0, scr_res.x - int(winRect.right - winRect.left) ) / 2;
		v_wnd_pos.y = Max( 0, scr_res.y - int(winRect.bottom - winRect.top) ) / 2;		
		
		_wnd = CreateWindowEx(	wndExtStyle,
								_GetClassName(),
								ENGINE_NAME,
								wndStyle,
								v_wnd_pos.x,
								v_wnd_pos.y,
								winRect.right - winRect.left,
								winRect.bottom - winRect.top,
								null,
								null,
								_GetInstance(),
								null );

		CHECK_ERR( _wnd != null );

		ShowWindow( _wnd, SW_HIDE );

		_dc = GetDC( _wnd );
		CHECK_ERR( _dc != null );

		_windowSize = v_wnd_size;
		_windowPos  = v_wnd_pos;

		//SetTimer( _wnd, _timer.id, 1000, null );

		return true;
	}
	
/*
=================================================
	_InitRawInput
=================================================
*/
	bool WindowsPlatform::_InitRawInput ()
	{
		RAWINPUTDEVICE	Rid[2];
		// usage params:
		// 0x02 - mouse, 0x04 - joystick, 0x05 - game pad, 0x06 - keyboard
		
		// mouse
		Rid[0].usUsagePage	= 0x01;
		Rid[0].usUsage		= 0x02;
		Rid[0].dwFlags		= 0;
		Rid[0].hwndTarget	= _wnd;
		
		// keyboard
		Rid[1].usUsagePage	= 0x01;
		Rid[1].usUsage		= 0x06;
		Rid[1].dwFlags		= 0;	// RIDEV_INPUTSINK | RIDEV_NOHOTKEYS | RIDEV_NOLEGACY | RIDEV_REMOVE;
		Rid[1].hwndTarget	= _wnd;
		
		CHECK_ERR( RegisterRawInputDevices( &Rid[1], 1, sizeof(Rid[0]) ) == TRUE );
		return true;
	}
	
/*
=================================================
	_InitOpenGL2
=================================================
*/
	bool WindowsPlatform::_InitOpenGL2 ()
	{
		PIXELFORMATDESCRIPTOR pfd = { sizeof(PIXELFORMATDESCRIPTOR), 1, PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL, PFD_TYPE_RGBA,
									  32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, PFD_MAIN_PLANE, 0, 0, 0, 0 };

		const uint	pixformat	= ChoosePixelFormat( _dc, &pfd );
		HGLRC		rc			= null;
		bool		res			= false;

		if ( pixformat != 0 )
		{
			SetPixelFormat( _dc, pixformat, &pfd );
			rc = wglCreateContext( _dc );

			if ( rc != null and wglMakeCurrent( _dc, rc ) == TRUE )
			{
				_ext.wglChoosePixelFormat	= (PFNWGLCHOOSEPIXELFORMATARBPROC) wglGetProcAddress("wglChoosePixelFormatARB");
				_ext.wglCreateContextAttribs= (PFNWGLCREATECONTEXTATTRIBSARBPROC) wglGetProcAddress("wglCreateContextAttribsARB");
		
				_ext.wglSwapInterval		= (PFNWGLSWAPINTERVALEXTPROC) wglGetProcAddress("wglSwapIntervalEXT");
				_ext.wglGetSwapInterval		= (PFNWGLGETSWAPINTERVALEXTPROC) wglGetProcAddress("wglGetSwapIntervalEXT");

				_ext.swapControlSupported	= (_ext.wglSwapInterval != null) and (_ext.wglGetSwapInterval != null);

				res = true;
			}
			wglMakeCurrent( _dc, null );

			wglDeleteContext( rc );
		}
		
		return res;
	}

/*
=================================================
	_InitOpenGL
=================================================
*/
	bool WindowsPlatform::_InitOpenGL (INOUT VideoSettings &vs)
	{
		if ( _dc == null )
			RETURN_ERR( "Window must be created before init OpenGL!" );

		CHECK_ERR( _InitOpenGL2() );

		CHECK_ERR( _ext.wglChoosePixelFormat != null );
		CHECK_ERR( _ext.wglCreateContextAttribs != null );
		

		// Chose Pixel Format //
		PIXELFORMATDESCRIPTOR pfd = { sizeof(PIXELFORMATDESCRIPTOR), 1, PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL, PFD_TYPE_RGBA,
									  32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, PFD_MAIN_PLANE, 0, 0, 0, 0 };

		uint	pixformat = 0;

		float	fa_attributes[] = {0.0, 0.0};

		int		a_attributes[] =
		{
			WGL_DRAW_TO_WINDOW_ARB,		true,
			WGL_SUPPORT_OPENGL_ARB,		true,
			WGL_ACCELERATION_ARB,		WGL_FULL_ACCELERATION_ARB,
			WGL_COLOR_BITS_ARB,			vs.AlphaBits() == 0 ? 24 : 32,
			WGL_ALPHA_BITS_ARB,			vs.AlphaBits() == 0 ? 1 : vs.AlphaBits(),
			WGL_DEPTH_BITS_ARB,			vs.depthBits,
			WGL_STENCIL_BITS_ARB,		vs.stencilBits,
			WGL_DOUBLE_BUFFER_ARB,		true,
			WGL_PIXEL_TYPE_ARB,			WGL_TYPE_RGBA_ARB,
			WGL_SAMPLE_BUFFERS_ARB,		vs.multiSamples > 1 ? true : false,
			WGL_SAMPLES_ARB,			vs.multiSamples,
			0,0
		};

		int rv = 0;

		while ( rv == 0 )
		{
			int		pixelFormat	= 0;
			uint	numFormats	= 0;
			int		valid		= _ext.wglChoosePixelFormat( _dc, a_attributes, fa_attributes, 1, &pixelFormat, &numFormats );

			if ( valid != 0 and numFormats > 0 )
				rv = pixelFormat;
			else
			{
				if ( a_attributes[21] != 0 )	// msaa
					a_attributes[21] >>= 1;
				else
				if ( a_attributes[13] != 0 )	// stencil buffer
					a_attributes[13] = 0;
				else
				if ( a_attributes[11] > 24 )	// depth buffer
					a_attributes[11] = 24;
				else
				if ( a_attributes[11] != 16 )	// depth buffer
					a_attributes[11] = 16;
				else
					break;
			}
		}

		if ( rv != 0 )
		{
			pixformat		= rv;
			vs.depthBits	= a_attributes[11];
			vs.stencilBits	= a_attributes[13];
			vs.multiSamples	= a_attributes[21];
				
			if ( not DescribePixelFormat( _dc, pixformat, sizeof(pfd), &pfd ) )
				LOG( "Can't describe pixel format", ELog::WARN );
		}


		// Create OpenGL Context //
		int	a_attribs[] =
		{
			WGL_CONTEXT_MAJOR_VERSION_ARB, 4,
			WGL_CONTEXT_MINOR_VERSION_ARB, 2,
			WGL_CONTEXT_FLAGS_ARB,         WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
			//WGL_CONTEXT_PROFILE_MASK_ARB,  WGL_CONTEXT_ES2_PROFILE_BIT_EXT,		// or
			//WGL_CONTEXT_PROFILE_MASK_ARB,  WGL_CONTEXT_CORE_PROFILE_BIT_ARB,		// or
			WGL_CONTEXT_PROFILE_MASK_ARB,  WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB,
			0
		};

		for (;;)
		{
			_rc = _ext.wglCreateContextAttribs( _dc, null, a_attribs );

			if ( _rc != null or a_attribs[1] == 2 )
				break;
				
			if ( a_attribs[3] == 0 )
			{
				a_attribs[1]--;
				a_attribs[3] = 9;
			}
			else
				a_attribs[3]--;
		}

		if ( _rc == null )
		{
			_rc = wglCreateContext( _dc );
		}
		CHECK_ERR( _rc != null );

		return true;
	}
	
/*
=================================================
	_ProcessMessage
=================================================
*/
	int WindowsPlatform::_ProcessMessage (UINT uMsg, WPARAM wParam, LPARAM lParam)
	{
		// WM_PAINT //
		if ( uMsg == WM_PAINT )
		{
			_Update( true );
		}
		else

		// WM_INPUT //
		if ( uMsg == WM_INPUT && _pause == 0 )
		{
			uint8	a_data[60];
			uint	u_size = sizeof(a_data);

			if ( GetRawInputData( (HRAWINPUT)lParam, RID_INPUT, a_data, &u_size, sizeof(RAWINPUTHEADER) ) != -1 )
			{
				RAWINPUT  *	p_data = (RAWINPUT *)a_data;

				// KEYBOARD //
				if ( p_data->header.dwType == RIM_TYPEKEYBOARD )
				{
					uint16	u_right = p_data->data.keyboard.Flags & RI_KEY_E0 ? e_winkey::right_offset : 0;
					
					if ( p_data->data.keyboard.VKey + u_right >= e_winkey::_COUNT )
						u_right = 0;

					bool	pressed = EnumNoCmp( p_data->data.keyboard.Flags, RI_KEY_BREAK );
					int		keyCode = p_data->data.keyboard.VKey + u_right;

					TSysEvent< ESystemEvent::RAW_KEY >	se( _GetTimestamp() );

					se.rawKey.code		= keyCode;
					se.rawKey.pressed	= pressed;

					SendEvent( se );
				}
			}
		}
		else

		// WM_MOUSEMOVE //
		// WM_LBUTTONUP //
		// WM_LBUTTONDOWN //
		if ( _pause == 0 and (
			(uMsg == WM_MOUSEMOVE or
			 uMsg == WM_LBUTTONDOWN or uMsg == WM_LBUTTONUP or
			 uMsg == WM_RBUTTONDOWN or uMsg == WM_RBUTTONUP or
			 uMsg == WM_MBUTTONDOWN or uMsg == WM_MBUTTONUP or
			 uMsg == WM_XBUTTONDOWN or uMsg == WM_XBUTTONUP ) ) )
		{
			if ( uMsg == WM_MOUSEMOVE )
			{
				if ( EnumCmp( wParam, MK_LBUTTON ) )	_OnTouchMotion( 0, LOWORD( lParam ), HIWORD( lParam ) );
				if ( EnumCmp( wParam, MK_RBUTTON ) )	_OnTouchMotion( 1, LOWORD( lParam ), HIWORD( lParam ) );
				if ( EnumCmp( wParam, MK_MBUTTON ) )	_OnTouchMotion( 2, LOWORD( lParam ), HIWORD( lParam ) );
				if ( EnumCmp( wParam, MK_XBUTTON1 ) )	_OnTouchMotion( 3, LOWORD( lParam ), HIWORD( lParam ) );
				if ( EnumCmp( wParam, MK_XBUTTON2 ) )	_OnTouchMotion( 4, LOWORD( lParam ), HIWORD( lParam ) );
			}
			else
			{
				if ( uMsg == WM_LBUTTONDOWN or uMsg == WM_LBUTTONUP )
					_OnTouch( 0, uMsg == WM_LBUTTONDOWN, LOWORD( lParam ), HIWORD( lParam ) );
				else
				if ( uMsg == WM_RBUTTONDOWN or uMsg == WM_RBUTTONUP )
					_OnTouch( 1, uMsg == WM_RBUTTONDOWN, LOWORD( lParam ), HIWORD( lParam ) );
				else
				if ( uMsg == WM_MBUTTONDOWN or uMsg == WM_MBUTTONUP )
					_OnTouch( 1, uMsg == WM_MBUTTONDOWN, LOWORD( lParam ), HIWORD( lParam ) );
				else
				if ( uMsg == WM_XBUTTONDOWN or uMsg == WM_XBUTTONUP )
					_OnTouch( 1, uMsg == WM_XBUTTONDOWN, LOWORD( lParam ), HIWORD( lParam ) );
			}
		}
		else
			
		// WM_TIMER //
		/*if ( uMsg == WM_TIMER && wParam == _timer.id )
		{
			_timer.lastFrames	= _timer.frames;
			_timer.frames		= 0;

			if ( _pause == 0 )
			{
				string	str(ENGINE_NAME);

				str << " [FPS: " << string().FormatI(_timer.lastFrames,10);
				str << ", rendering: ";
				StringUtils::TimeToStr( str, float(_profiler.renderingTime) );
				str << ", primitives: " << string().FormatI(_profiler.drawnVertices, 10) << "]";

				SetWindowText( _wnd, str.cstr() );
			}
		}
		else*/

		// WM_MOVE //
		if ( uMsg == WM_MOVE )
		{
			RECT	winRect = {0,0,0,0};
			GetWindowRect( _wnd, &winRect );
			ivec2	new_pos = ivec2( winRect.left, winRect.top );

			_OnMove( new_pos - _windowPos );
			_windowPos = new_pos;
		}
		else

		// WM_KILLPOCUS //
		if ( uMsg == WM_KILLFOCUS )
		{
			_pause |= NO_FOCUS;
			_VisibilityChanged( false );
		}
		else

		// WM_SETFOCUS //
		if ( uMsg == WM_SETFOCUS )
		{
			_pause &= ~NO_FOCUS;
			_VisibilityChanged( true );
		}
		else
			

		// WM_SIZE //
		if ( uMsg == WM_SIZE )
		{
			if ( wParam != SIZE_MINIMIZED )
			{
				_OnResized( LOWORD( lParam ), HIWORD( lParam ) );
			}
		}
		else

		// WM_SYSCOMMAND //
		if ( uMsg == WM_SYSCOMMAND && (wParam == SC_SCREENSAVE || wParam == SC_MONITORPOWER) )
			return 0;
		else

		// WM_CLOSE //
		if ( uMsg == WM_CLOSE ) {
			PostMessageA( _wnd, WM_QUIT, 0, 0 );
			return 1;
		}

		return DefWindowProcA( _wnd, uMsg, wParam, lParam );
	}
	
/*
=================================================
	_MsgProc
=================================================
*/
	LRESULT CALLBACK WindowsPlatform::_MsgProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
	{
		WindowsPlatform *	wp = (WindowsPlatform *)GetWindowLongPtrA( hWnd, GWLP_USERDATA );
		return wp->_ProcessMessage( uMsg, wParam, lParam );
	}

/*
=================================================
	_Destroy
=================================================
*/
	void WindowsPlatform::_Destroy ()
	{
		// TODO: save state
		// _application->SaveState( data );

		if ( _dc != null )
		{
			if ( _rc != null )
			{
				wglMakeCurrent( _dc, null );
				wglDeleteContext( _rc );
				_rc = null;
			}

			ReleaseDC( _wnd, _dc );
			_dc = null;
		}

		if ( _wnd != null )
		{
			DestroyWindow( _wnd );
			_wnd = null;
		}

		UnregisterClass( _GetClassName(), _GetInstance() );
	}
	
/*
=================================================
	_Init
=================================================
*/
	bool WindowsPlatform::_Init ()
	{
		CHECK_ERR( g_CreateMainApplication != null );
		g_CreateMainApplication( this, _application.ref() );

		CHECK_ERR( _application.IsNotNull() );

		TSysEvent< ESystemEvent::APPLICATION >	ev;

		ev.app.type = SysEvent::Application::CREATED;
		SendEvent( ev );

		return true;
	}
	
/*
=================================================
	_Quit
=================================================
*/
	void WindowsPlatform::_Quit ()
	{
	}
	
/*
=================================================
	_EnterForeground
=================================================
*/
	void WindowsPlatform::_EnterForeground (bool completed)
	{
		TSysEvent< ESystemEvent::APPLICATION >	ev;

		ev.app.type		 = SysEvent::Application::ENTER_FOREGROUND;
		ev.app.completed = completed;
		
		SendEvent( ev );
	}
	
/*
=================================================
	_EnterBackground
=================================================
*/
	void WindowsPlatform::_EnterBackground (bool completed)
	{
		TSysEvent< ESystemEvent::APPLICATION >	ev;

		ev.app.type		 = SysEvent::Application::ENTER_BACKGROUND;
		ev.app.completed = completed;
		
		SendEvent( ev );
	}

/*
=================================================
	_VisibilityChanged
=================================================
*/
	void WindowsPlatform::_VisibilityChanged (bool isVisible)
	{
		TSysEvent< ESystemEvent::WINDOW >	ev;

		ev.window.type = isVisible ? SysEvent::Window::SHOWN : SysEvent::Window::HIDDEN;

		SendEvent( ev );
	}
	
/*
=================================================
	_OnResized
=================================================
*/
	void WindowsPlatform::_OnResized (uint w, uint h)
	{
		_windowSize.x = w;
		_windowSize.y = h;

		_device.GetDisplayRef().SetResolution( _windowSize );

		TSysEvent< ESystemEvent::WINDOW >	se;

		se.window.type		= SysEvent::Window::RESIZED;
		se.window.width		= _windowSize.x;
		se.window.height	= _windowSize.y;

		SendEvent( se );
	}
	
/*
=================================================
	_OnKey
=================================================
*/
	inline void WindowsPlatform::_OnKey (int keyCode, bool down)
	{
		TSysEvent< ESystemEvent::RAW_KEY >	se( _GetTimestamp() );

		se.rawKey.code		= keyCode;
		se.rawKey.pressed	= down;

		SendEvent( se );
	}
	
/*
=================================================
	_OnTouch
=================================================
*/
	inline void WindowsPlatform::_OnTouch (int id, bool down, int x, int y)
	{
		TSysEvent< ESystemEvent::RAW_TOUCH >	se( _GetTimestamp() );

		se.rawTouch.id		 = id;
		se.rawTouch.x		 = float( x );
		se.rawTouch.y		 = float( _windowSize.y - y );
		se.rawTouch.pressure = down ? 1.0f : 0.0f;
		se.rawTouch.motion	 = false;
		se.rawTouch.pressed	 = down;
		
		SendEvent( se );
	}
	
/*
=================================================
	_OnTouchMotion
=================================================
*/
	inline void WindowsPlatform::_OnTouchMotion (int id, int x, int y)
	{
		TSysEvent< ESystemEvent::RAW_TOUCH >	se( _GetTimestamp() );

		se.rawTouch.id		 = id;
		se.rawTouch.x		 = float( x );
		se.rawTouch.y		 = float( _windowSize.y - y );
		se.rawTouch.pressure = 1.0f;
		se.rawTouch.motion	 = true;
		se.rawTouch.pressed	 = true;
		
		SendEvent( se );
	}
	
/*
=================================================
	_Resize
=================================================
*/
	void WindowsPlatform::_Resize (ivec2 size)
	{
		ivec2 const	res = _GetScreenResolution().To<ivec2>();
		ivec2		pos;

		size = Clamp( size, ivec2(12), res );

		DWORD dwStyle	= (DWORD) GetWindowLongA( _wnd, GWL_STYLE );
		DWORD dwExStyle	= (DWORD) GetWindowLongA( _wnd, GWL_EXSTYLE );

		RECT	winRect = { 0, 0, size.x, size.y };
		AdjustWindowRectEx( &winRect, dwStyle, FALSE, dwExStyle );
				
		pos.x = Max( 0, res.x - int(winRect.right  - winRect.left) ) / 2;
		pos.y = Max( 0, res.y - int(winRect.bottom - winRect.top) ) / 2;

		SetWindowPos( _wnd, HWND_TOP, pos.x, pos.y, winRect.right - winRect.left,
						winRect.bottom - winRect.top, SWP_FRAMECHANGED );
	}

/*
=================================================
	_EventLoop
=================================================
*/
	bool WindowsPlatform::_EventLoop ()
	{
		if ( _terminated )
			return true;

		MSG		msg	= {0};
		
		_isLooping = true;
		
		SetWindowLongPtrA( _wnd, GWLP_USERDATA, (LONG_PTR) this );
		SetWindowLongPtrA( _wnd, GWLP_WNDPROC,  (LONG_PTR) &_MsgProc );

		SetForegroundWindow( _wnd );
		ShowWindow( _wnd, SW_SHOWNA );

		while ( _isLooping )
		{
			while ( PeekMessageA( &msg, 0, 0, 0, PM_REMOVE ) )
			{
				if ( WM_QUIT == msg.message )
					_isLooping = false;
				else
					DispatchMessageA( &msg );
			}

			_Update( false );
		}
		
		SetWindowLongPtrA( _wnd, GWLP_WNDPROC, (LONG_PTR) DefWindowProcA );

		CHECK_ERR( ((int)msg.wParam) != -1 );
		return true;
	}
	
/*
=================================================
	_BackgroundLoop
=================================================
*/
	void WindowsPlatform::_BackgroundLoop ()
	{
		while ( _backgroundMode )
		{
			_Update( false );
			Thread::Yield();
		}
	}
	
/*
=================================================
	_Update
=================================================
*/
	void WindowsPlatform::_Update (bool redraw)
	{
		const bool	has_rc = ( _rc != null and _dc != null );

		if ( has_rc )
		{
			wglMakeCurrent( _dc, _rc );
		}

		_application->Flush();
		_application->ProcessMessages();

		TSysEvent< ESystemEvent::UPDATE >	ev( _GetTimestamp() );

		ev.update.timeDelta			= (float) (ev.timestamp - _lastUpdateTime).ToDouble().GetSec();
		ev.update.redraw			= redraw;
		ev.update.renderAvailable	= has_rc;

		_lastUpdateTime = ev.timestamp;
		
		SendEvent( ev );
	}

	
}	// Platforms
}	// Engine


#if not defined( ANDROID_EMULATOR )
/*
=================================================
	main
=================================================
*/
int main (int argc, char *argv[])
{
	using namespace Engine;
	using namespace Platforms;

	WindowsPlatform::CreateInstance();
	int ret = WindowsPlatform::GetInstance()->Loop();

	Referenced::s_ChenckNotReleasedObjects();

	return ret;
}
#endif	// not ANDROID_EMULATOR


#endif	// PLATFORM_WINDOWS
